import time
import paramiko
import requests
from requests.auth import HTTPBasicAuth
import urllib3
from typing import Dict, List, Optional

# 禁用HTTPS证书警告（生产环境应配置真实证书）
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


class BMCTester:
    def __init__(self, bmc_ip: str, bmc_username: str, bmc_password: str,
                 os_username: str, os_password: str, os_ip: str):
        """
        初始化BMC测试器

        :param bmc_ip: BMC管理IP地址
        :param bmc_username: BMC用户名
        :param bmc_password: BMC密码
        :param os_username: 操作系统用户名
        :param os_password: 操作系统密码
        :param os_ip: 操作系统IP地址
        """
        self.bmc_ip = bmc_ip
        self.bmc_username = bmc_username
        self.bmc_password = bmc_password
        self.os_username = os_username
        self.os_password = os_password
        self.os_ip = os_ip
        self.ssh_client: Optional[paramiko.SSHClient] = None
        self.bmc_session: Optional[requests.Session] = None
        self.vendor_endpoints = self._get_vendor_endpoints()

    def _get_vendor_endpoints(self) -> Dict[str, List[str]]:
        """返回不同厂商的Redfish API端点"""
        return {
            "power_control": [
                "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset",  # 标准路径
                "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset",  # 华为/某些厂商
                "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset",  # Dell
            ],
            "logs": [
                "/redfish/v1/Managers/bmc/LogServices/SEL/Entries",  # 标准路径
                "/redfish/v1/Managers/Self/LogServices/SEL/Entries",  # 某些厂商
                "/redfish/v1/Managers/1/LogServices/SEL/Entries",  # 华为/其他
            ],
            "system_info": [
                "/redfish/v1/Systems/system",  # 标准路径
                "/redfish/v1/Systems/1",  # 替代路径
            ]
        }

    def connect_bmc(self) -> bool:
        """连接到BMC的Redfish接口"""
        try:
            self.bmc_session = requests.Session()
            self.bmc_session.auth = HTTPBasicAuth(self.bmc_username, self.bmc_password)
            self.bmc_session.verify = False  # 生产环境应配置真实证书

            # 测试连接是否有效
            test_url = f"https://{self.bmc_ip}/redfish/v1"
            response = self.bmc_session.get(test_url, timeout=10)
            if response.status_code != 200:
                print(f"BMC连接测试失败，状态码: {response.status_code}")
                return False

            print(f"成功连接到BMC: {self.bmc_ip}")
            return True
        except Exception as e:
            print(f"连接BMC失败: {str(e)}")
            return False

    def connect_os(self) -> bool:
        """连接到操作系统SSH"""
        try:
            self.ssh_client = paramiko.SSHClient()
            self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.ssh_client.connect(
                hostname=self.os_ip,
                username=self.os_username,
                password=self.os_password,
                timeout=10
            )
            print(f"成功连接到操作系统: {self.os_ip}")
            return True
        except Exception as e:
            print(f"连接操作系统失败: {str(e)}")
            return False

    def disconnect(self):
        """断开所有连接"""
        if self.ssh_client:
            self.ssh_client.close()
            self.ssh_client = None
        if self.bmc_session:
            self.bmc_session.close()
            self.bmc_session = None

    def _try_endpoints(self, endpoint_type: str, payload: Optional[Dict] = None,
                       method: str = "GET") -> Optional[requests.Response]:
        """
        尝试多个可能的API端点

        :param endpoint_type: 端点类型 (power_control/logs/system_info)
        :param payload: POST请求的负载
        :param method: HTTP方法 (GET/POST)
        :return: 响应对象或None
        """
        endpoints = self.vendor_endpoints.get(endpoint_type, [])

        for endpoint in endpoints:
            url = f"https://{self.bmc_ip}{endpoint}"
            try:
                if method.upper() == "POST":
                    response = self.bmc_session.post(url, json=payload, timeout=30)
                else:
                    response = self.bmc_session.get(url, timeout=30)

                print(f"尝试端点: {url} -> 状态码: {response.status_code}")

                if response.status_code in [200, 204]:
                    return response
                else:
                    print(f"请求失败，状态码: {response.status_code}, 响应: {response.text[:200]}...")
            except Exception as e:
                print(f"请求出错: {str(e)}")

        return None

    def send_bmc_command(self, command: str) -> bool:
        """
        发送BMC电源控制命令

        :param command: 命令 (power_on/power_off/power_reset)
        :return: 是否成功
        """
        command_map = {
            "power_on": "On",
            "power_off": "ForceOff",
            "power_reset": "ForceRestart"
        }

        if command not in command_map:
            print(f"无效命令: {command}")
            return False

        payload = {"ResetType": command_map[command]}
        response = self._try_endpoints("power_control", payload, "POST")

        if response:
            print(f"成功执行BMC命令: {command}")
            return True

        print(f"所有电源控制端点尝试失败")
        return False

    def check_os_status(self, timeout: int = 300, interval: int = 10) -> bool:
        """
        检查操作系统是否可用

        :param timeout: 超时时间(秒)
        :param interval: 检查间隔(秒)
        :return: 是否成功
        """
        start_time = time.time()

        while time.time() - start_time < timeout:
            try:
                with paramiko.SSHClient() as ssh:
                    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    ssh.connect(
                        hostname=self.os_ip,
                        username=self.os_username,
                        password=self.os_password,
                        timeout=10
                    )

                    # 执行更全面的系统检查
                    commands = [
                        'echo "System is up"',
                        'uptime',
                        'hostname'
                    ]

                    for cmd in commands:
                        stdin, stdout, stderr = ssh.exec_command(cmd)
                        output = stdout.read().decode().strip()
                        print(f"系统检查: {cmd} -> {output}")

                    return True
            except Exception as e:
                print(f"系统检查失败，等待重试... ({str(e)})")
                time.sleep(interval)

        print("系统状态检查超时")
        return False

    def collect_bmc_logs(self, log_file: str = "bmc_logs.txt") -> bool:
        """
        收集BMC日志

        :param log_file: 日志文件名
        :return: 是否成功
        """
        response = self._try_endpoints("logs")

        if not response:
            print("无法获取BMC日志")
            return False

        try:
            logs = response.json()
            with open(log_file, 'w') as f:
                if 'Members' in logs:
                    for entry in logs['Members']:
                        log_line = (
                            f"{entry.get('Created', 'N/A')}: "
                            f"{entry.get('Message', 'No message')} | "
                            f"Severity: {entry.get('Severity', 'N/A')}"
                        )
                        f.write(log_line + "\n")
                else:
                    f.write(json.dumps(logs, indent=2))

            print(f"成功保存BMC日志到 {log_file}")
            return True
        except Exception as e:
            print(f"处理日志时出错: {str(e)}")
            return False

    def get_system_info(self) -> Optional[Dict]:
        """获取系统信息"""
        response = self._try_endpoints("system_info")
        return response.json() if response else None

    def run_test_cycle(self, test_config: Dict) -> bool:
        """
        运行测试循环（根据配置执行测试项目）

        :param test_config: 测试配置字典
        :return: 是否全部成功
        """
        if not self.connect_bmc():
            return False

        test_results = []
        cycles = test_config.get("cycles", 1)
        delay = test_config.get("delay_between_cycles", 30)

        for i in range(1, cycles + 1):
            print(f"\n=== 开始测试循环 {i}/{cycles} ===")

            # 打印系统信息
            if system_info := self.get_system_info():
                print(f"系统信息: {system_info.get('Name')} | {system_info.get('Model')}")

            # 测试开机（如果配置启用）
            if test_config.get("test_power_on", True):
                print("\n[1/3] 测试开机...")
                if not self.send_bmc_command("power_on"):
                    test_results.append(f"循环 {i}: 开机失败")
                    continue

                if not self.wait_for_os():
                    test_results.append(f"循环 {i}: 开机后系统未就绪")
                    continue

            # 测试重启（如果配置启用）
            if test_config.get("test_reboot", True):
                print("\n[2/3] 测试重启...")
                if not self.send_bmc_command("power_reset"):
                    test_results.append(f"循环 {i}: 重启失败")
                    continue

                if not self.wait_for_os():
                    test_results.append(f"循环 {i}: 重启后系统未就绪")
                    continue

            # 测试关机（如果配置启用）
            if test_config.get("test_power_off", True):
                print("\n[3/3] 测试关机...")
                if not self.send_bmc_command("power_off"):
                    test_results.append(f"循环 {i}: 关机失败")
                    continue

                # 等待关机完成
                time.sleep(30)

            # 准备下一次循环（如果不是最后一个循环）
            if i < cycles:
                print("\n准备下一次测试循环...")
                if test_config.get("test_power_on", True):  # 只有启用了开机测试才需要准备
                    if not self.send_bmc_command("power_on"):
                        test_results.append(f"循环 {i}: 准备下次循环时开机失败")
                        break

                    if not self.wait_for_os():
                        test_results.append(f"循环 {i}: 准备下次循环时系统未就绪")
                        break

            test_results.append(f"循环 {i}: 成功完成")
            time.sleep(delay)

        # 收集日志
        if test_config.get("collect_logs", True):
            print("\n收集BMC日志...")
            self.collect_bmc_logs()

        # 打印结果
        print("\n=== 测试结果 ===")
        for result in test_results:
            print(f"- {result}")

        self.disconnect()
        return all("成功" in result for result in test_results)

    def wait_for_os(self, timeout: int = 300) -> bool:
        """等待操作系统可用"""
        print(f"等待系统启动，超时: {timeout}秒...")
        return self.check_os_status(timeout=timeout)


if __name__ == "__main__":
    # 配置参数（建议从环境变量或配置文件读取）
    config = {
        # 连接配置
        "BMC_IP": "192.168.2.16",
        "BMC_USERNAME": "admin",
        "BMC_PASSWORD": "admin",
        "OS_IP": "192.168.2.18",
        "OS_USERNAME": "root",
        "OS_PASSWORD": "haiguang@123",

        # 测试配置
        "test_options": {
            "cycles": 3,  # 测试循环次数
            "delay_between_cycles": 30,  # 循环间隔(秒)
            "test_power_on": False,  # 是否测试开机
            "test_power_off": False,  # 是否测试关机
            "test_reboot": True,  # 是否测试重启
            "collect_logs": True  # 是否收集日志
        }
    }

    # 创建测试器实例
    tester = BMCTester(
        bmc_ip=config["BMC_IP"],
        bmc_username=config["BMC_USERNAME"],
        bmc_password=config["BMC_PASSWORD"],
        os_ip=config["OS_IP"],
        os_username=config["OS_USERNAME"],
        os_password=config["OS_PASSWORD"]
    )

    # 运行测试（传入测试配置）
    success = tester.run_test_cycle(test_config=config["test_options"])

    if success:
        print("\n✅ 所有测试循环成功完成!")
    else:
        print("\n❌ 测试过程中出现故障，请检查日志!")